﻿using Microsoft.AspNetCore.Mvc;
using Routine.Api.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Routine.Api.Models;
using AutoMapper;
using Routine.Api.DtoParameters;
using Routine.Api.Entities;
using Routine.Api.Helpers;
using Microsoft.Net.Http.Headers;
using Marvin.Cache.Headers;

namespace Routine.Api.Controllers         //这一层是最外的一层;
{
    [ApiController]                                  //要求使用属性路由。    错误代码的详细信息
    [Route("api/companies")]//用公司复数的级别;
    public class CompaniesControllers:ControllerBase  //api接口的就可以。MVC用于Controll。里面很多处理controller的方法
    {
        private readonly ICompanyRepository _companyRepository;
        private readonly IMapper _mapper;

        //状态码404没找到，405方法不允许；

        public CompaniesControllers(ICompanyRepository companyRepository, IMapper mapper)
        {
           _companyRepository = companyRepository                    //注入
            ??throw new ArgumentNullException(nameof(companyRepository));     //检测是否为空，空的话抛出异常。    ?代表可空            
            _mapper = mapper
                ??throw new ArgumentNullException(nameof(mapper));
        }


        [HttpGet(Name =nameof(GetCompanies))]               //要标注好是否是Get
        [HttpHead]          //这样写的话，即支持HttpGet，也支持HttpHead；只不过不返回body
                            //Head 和 get 几乎是一样的，只是Head的API不返回body
                            //Head 可以检查资源是否存在，或者资源是否被更改；

     //   [ResponseCache(Duration =120)]//设置缓存，缓存时间是120s
     [HttpCacheExpiration(CacheLocation =CacheLocation.Public,MaxAge =1800)]//用第三方库进行过期模型；
     [HttpCacheValidation(MustRevalidate =true)]//验证模型是false；
        public async Task<IActionResult> GetCompanies(
                                                           //IAction接口表示返回的接口，一般是Json格式。
                                                         //如果返回类型明确的话，把具体的T写进去，可以直接在 ActionResult<T>把T写进去。
          [FromQuery]CompanyDtoParameters parmeters)//有多个查询条件时候，采用Parmeters的方法来传参数；
        {

      //  throw new Exception("AN exception");  //可以在此处抛出异常。


            var companies = await _companyRepository.GetCompaniesAsync(parmeters);//看这个穿进去哪个方法；

            //return new JsonResult(companies);                //此处的JSONResolt要new一个。
            //return NotFound();   //404表示要找的东西没有找到。


            //var companyDtos = new List<CompanyDto>();//list（就他一个类，剩下的都是接口） <  Ilist   < Iclollerction  < IEnum
            //foreach (var item in companies)
            //{
            //    companyDtos.Add(new CompanyDto   //数组是用 add添加；
            //    {
            //        Id = item.Id,
            //        Name = item.Name
            //    }            //数组少的话可以一个一个的添加，多的了话就有点麻烦了。
            //    );     
            //}

            //用AutoMaper如何用；
            var companyDtos = _mapper.Map<IEnumerable<CompanyDto>>(companies);//<>是目标类型。（）是原类型。
      


            //  return companyDtos;  直接返回类型也可以。
            return Ok(companyDtos.ShapeData(parmeters.Fields)); //实际返回类型是List<CompanyDto>。如果加OK就是使用的IActionResult。
            //后边直接调用该方法；
        }



        //[Route("{companyId}")]

        /*
         api数据源是指数据从哪里来的：
         [FromBody]请求的Body；
         [FromForm]请求的Body中的form数据；
         [FromHeader]请的Header；
         [FromQuery],Query string 参数； query疑问 【计】查询
         [FromRoute]，当前请求中的路由数据。
         [FromService]，作为Action参数而注入的服务。
          
            例如：GetCompany（[FromRoute]Guid companyId）
                  GetCompany（[FromQuery]Guid companyId）

            ApiController表示更改后的规则

            过滤 针对集合的  (一个完整的集合，然后把不匹配的删除)。只能针对资源的字段进行过滤，比如DTO，而不能是Entity
            URL：例如 GET/api/companies? type=State-owned&region=Europe
            搜索 针对集合的（先给个空集合，然后往空集合里面添加进来的）不会把要匹配的字段传过去，而是把要搜索的值穿过去。
            URL ：GET /api/companyes? q=xxx ；
         */

        [HttpGet("{companyId}", Name = nameof(GetCompany))]//Name是给路由起名；
        public async Task<IActionResult> GetCompany(Guid companyId, string fields,[FromHeader(Name ="Accept")] string mediaType)//寻找这个companyId是从哪里来的。把这个 mediaType找到；
        {
            if (!MediaTypeHeaderValue.TryParse(mediaType,out MediaTypeHeaderValue parsedMediaType))//out后的 parsedMediaType,是解析之后的。
            {
                return BadRequest();
            }
            var company = await _companyRepository.GetCompanyAsync(companyId);
            if (company==null)
            {
                return NotFound();
            }
            if (parsedMediaType.MediaType == "application/vnd.company.hateoas+json")//vnd mediaType
            {
                var links = CreateLinksForCompany(companyId, fields);
                var LinkedDict = _mapper.Map<CompanyDto>(company).ShapeData(fields)
                    as IDictionary<string, object>;
                LinkedDict.Add("links", links);
                // return Ok(_mapper.Map<CompanyDto>(company));
                return Ok(LinkedDict);
            }
            return Ok(_mapper.Map<CompanyDto>(company).ShapeData(fields));
        }


        [HttpPost(Name =nameof(CreateCompany))]
        public async Task<ActionResult<CompanyDto>> CreateCompany([FromBody]CompanyAddDto company)//传进去的是addDto
            //更改过后，通过他可以直接创建父子资源；
            //如果穿进来的请求的body里面有Employee这个属性，那么就就可以反串行化这里面；
        {
            var entity = _mapper.Map<Company>(company);  //把company转化为 Company
                                                         //在Profiles设置映射关系;
            _companyRepository.AddCompany(entity);
            await _companyRepository.SaveAsync();//这个框架不需要抛出异常的；

            var returndto = _mapper.Map<CompanyDto>(entity);//映射;
            return CreatedAtRoute(nameof(GetCompany),new { companyId=returndto.Id},returndto);
            //post成功应该返回201,用CreatedAtRoute可以返回带着地址的URL
            //CreatedAtRoute的方法名字，这个参数里面的东西是什么意思；
        }


        //HTTP方法包括：
        //hTTP GET post(添加) patch（局部更新） put（替换） delete（删除）

        //写try catch语句的话，对性能影响较大；
        //串行化是指将对象存储到介质或者通过网络传输；
        //反串行化是从这些数据重新构建一个与原始对象状态相同的对象。也可以说是得到一个副本。

        [HttpDelete("{companyId}",Name =nameof(DeleteCompany))]
        public async Task<IActionResult> DeleteCompany(Guid companyId)
        {
            var companyEntity = await _companyRepository.GetCompanyAsync(companyId);
            if (companyEntity == null)
            {
                return NotFound();
            }
           // await _companyRepository.GetEmployeeAsync(companyId, null, null);
            _companyRepository.DeleteCompany(companyEntity);
            await _companyRepository.SaveAsync();
            return NotFound();
        }

        [HttpOptions] //这个是用来表示都是能进行什么操作的；
        public IActionResult GetCompaniesOptions()//不操作数据库就不需要用异步；
        {
            Response.Headers.Add("Allow","GET,POST,OPTIONS");//中间不要多加空格、
                return Ok();
        }
        private IEnumerable<LinkDto> CreateLinksForCompany(Guid companyId,string fields)
        {
            var links = new List<LinkDto>();
            if (string.IsNullOrWhiteSpace(fields))
            {
                links.Add(new LinkDto(Url.Link(nameof(GetCompany), new{ companyId }),"self", "GET"));
                //直接使用Url.Link（）就可以使用生成路由地址。
            }
            else
            {
                links.Add(new LinkDto(Url.Link(nameof(GetCompany), new { companyId,fields}), "self", "GET"));
            }
            links.Add(new LinkDto(Url.Link(nameof(DeleteCompany), new { companyId }), "delete_company", "GET"));
            links.Add(new LinkDto(Url.Link(nameof(EmployeesController.CreateEmployeeForCompany),
                new { companyId }), "create_employee_for_company", "POST"));
            return links;
        }


        /*
         缓存是一个独立的组件，存在于API和API消费者之间；
         客户端缓存：存在于浏览器上，并且是私有的；
         网关缓存：位于服务器端，所有的API消费者都会共享这个缓存（别名 反向代理服务器缓存，  HTTP加速器）
         代理缓存，位于网络的其他地方，既不在客户端，也不在服务器端，这种缓存经常被大型企业使用，用来服务大规模用户；
         缓存服务器；直接使用CDN；
         */
    }
}
